﻿using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Linq.Expressions;

namespace Microsoft.EntityFrameworkCore
{
    /// <summary>
    /// Converts nullable enum values to and from their string representation.
    /// </summary>
    /// <typeparam name="TEnumNullable">nullable enum tpe</typeparam>
    public class NullableEnumToStringConverter<TEnumNullable> : ValueConverter<TEnumNullable, string>
    {
        public NullableEnumToStringConverter([CanBeNull] ConverterMappingHints mappingHints = null)
            : base(
                ToEnumString(),
                ToEnumNullable(),
                mappingHints)
        {

        }

        protected static Expression<Func<TEnumNullable, string>> ToEnumString()
        {
            var modelType = typeof(TEnumNullable);

            if (!modelType.IsNullableValueType() || !modelType.UnwrapNullableType().IsEnum)
            {
                throw new InvalidOperationException(
                   CoreStrings.ConverterBadType(
                       typeof(NullableEnumToStringConverter<TEnumNullable>).ShortDisplayName(),
                       modelType.ShortDisplayName(),
                       "nullable enum types"));
            }

            return v => v == null ? null : v.ToString();
        }

        private static Expression<Func<string, TEnumNullable>> ToEnumNullable()
        {
            return v => ConvertToEnumNullable(v);
        }

        private static TEnumNullable ConvertToEnumNullable(string value)
        {
            var underlyingModelType = typeof(TEnumNullable).UnwrapNullableType();
            var defaultValue = Activator.CreateInstance<TEnumNullable>();

            return string.IsNullOrWhiteSpace(value)
             ? defaultValue
             : TryPareseEnum(underlyingModelType, value, true, out var result)
                    ? (TEnumNullable)result
                    : ulong.TryParse(value, out var ulongValue)
                        ? (TEnumNullable)(object)ulongValue
                        : long.TryParse(value, out var longValue)
                            ? (TEnumNullable)(object)longValue
                            : defaultValue;
        }

        private static bool TryPareseEnum(Type type, string value, bool isIgnoreCase, out object result)
        {
           return Enum.TryParse(type, value, isIgnoreCase,out result);
        }
    }
}
